/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2001 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef __sel_rwops_h__
#define __sel_rwops_h__

#include "sdl.h"
#include <string>
#include <stack>

namespace SEL
{

//! General interface class to read and write data sources.
/*!
  The advantage of RWops is that the same code can be used
  for work with files, memory and can be extended to other
  data sources.
 */
class RWops
{
public:
	//! Default (empty) constructor
	RWops ();

	//! Destroy the RWops instance.
	virtual ~RWops ();

	//! Return the wrapped SDL::RWops stucture.
	SDL::RWops *rwops () 			{ return m_rwops; }

	//! Return the wrapped constant SDL::RWops stucture.
	const SDL::RWops *rwops () const 	{ return m_rwops; }

	//! Return the virtual file begin offset.
	/*!
	  The virtual file begin offset affects the seek and tell functions.

	  For example:
	  \code
	  RWops ops(...);
	  ops.seek(2, SEEK_SET);  // move to the (absolute) offset 2
	  ops.setVirtualBegin (20); // set the virtual file begin at offset 20
	  ops.seek(2, SEEK_SET); // move to the (absolute) offset 22
	  \endcode
	  \sa setVirtualBegin;
	 */
	int virtualBegin () const		{ return m_virtual_begin; }

	//! Set the virtual file begin offset.
	/*!
	  The virtual file begin offset affects the seek and tell functions.
	  \param offset the new offset. Set this to 0 if you want the virtual
	  file and the real file to have common beginning.
	  \sa virtualBegin ()
	 */
	void setVirtualBegin (int offset);

	//! Return the virtual file size.
	/*!
	  The virtual file size affects the seek function:
	  \code
	  RWops ops (...);
	  ops.seek (0, SEEK_END); // move to the end of file
	  ops.setVirtualSize (200); // set the (virtual) size of file to be 200 bytes.
	  ops.seek (0, SEEK_END); // move to the offset 200 (which is the new end of the file)
	  \endcode
	 */
	int virtualSize () const		{ return m_virtual_size; }

	//! Set the virtual file size.
	/*!
	  The virtual file size affects the seek function:
	  \param size the new file size. If you want to disable virtual
	  size set it to 0.
	  \sa virtualSize ()
	 */
	void setVirtualSize (int size);

	//! Seek to specified \a offset relative to \a whence.
	/*!
	  \return The final offset in the data source.
	  \param offset the offset.
	  \param whece one of STDIO's whence values (SEEK_SET, SEEK_CUR,
	  SEEK_END).
         */
	int seek (int offset, int whence = SEEK_SET);

	//! Return the current position in the data source.
	int tell () const;

	//! Push the current position.
	/*!
	  The position can be restored with RWops::popPosition ().
	  \sa popPosition ()
	 */
	void pushPosition ();

	//! Restore the previous pushed position.
	/*!
	  \return the new absolute position.
	  \sa pushPosition ()
	 */
	int popPosition ();

	//! Read a block from the data source.
	/*!
	  Reads up to \a maxnum objects each of size \a size from the
	  data source to the area pointed at by \a ptr.
	  \return the number of objects read, or -1 if the read failed.
	  \param ptr the pointer to the area.
	  \param size the size of each object.
	  \param maxnum the number of objects.
	 */
	int read (void *ptr, int size, int maxnum = 1);

	//! Write a block to the data source.
	/*!
	  Writes exactly \a num objects each of size \a size from the area
	  pointed at by \a ptr to the data source.
	  \return \a num, or -1 if the write failed.
	  \param ptr the pointer to the area.
	  \param size the size of each object.
	  \param num the number of objects.
	 */
	int write (const void *ptr, int size, int num = 1);

	//! Close the RWops structure.
	int close ();

	//! \name RWops implementation
	//@{

	//! Seek to specified \a offset relative to \a whence.
	/*!
	  \return The final offset in the data source.
	  \param offset the offset.
	  \param whece one of STDIO's whence values (SEEK_SET, SEEK_CUR,
	  SEEK_END).
	  \note This function is not to be called directly. It's just
	  for derivation. It will be called automatically on
	  call to RWops::seek ();
         */
	virtual int doSeek (int offset, int whence) = 0;

	//! Read a block from the data source.
	/*!
	  Reads up to \a maxnum objects each of size \a size from the
	  data source to the area pointed at by \a ptr.
	  \return the number of objects read, or -1 if the read failed.
	  \param ptr the pointer to the area.
	  \param size the size of each object.
	  \param maxnum the number of objects.
	  \note This function is not to be called directly. It's just
	  for derivation. It will be called automatically on
	  call to RWops::read ();
	 */
	virtual int doRead (void *ptr, int size, int maxnum) = 0;

	//! Write a block to the data source.
	/*!
	  Writes exactly \a num objects each of size \a size from the area
	  pointed at by \a ptr to the data source.
	  \return \a num, or -1 if the write failed.
	  \param ptr the pointer to the area.
	  \param size the size of each object.
	  \param num the number of objects.
	  \note This function is not to be called directly. It's just
	  for derivation. It will be called automatically on
	  call to RWops::write ();
	 */
	virtual int doWrite (const void *ptr, int size, int num) = 0;

	//! Close the data source structure.
	/*!
	  \note This function is not to be called directly. It's just
	  for derivation. It will be called automatically on
	  call to RWops::close ();
	 */
	virtual int doClose () = 0;
	//@}

	//! Fast get char function.
	/*!
	  This function affects the state.
	 */
	virtual char getChar () 		{ char ch; operator>>(ch); return ch; }

	//! Read a 16 bit little endian number.
	/*!
	  If the host is big endian, the number will be properly
	  converted.
	 */
	Uint16 readLE16 ();

	//! Read a 16 bit big endian number.
	/*!
	  If the host is little endian, the number will be properly
	  converted.
	 */
	Uint16 readBE16 ();

	//! Read a 32 bit little endian number.
	/*!
	  If the host is big endian, the number will be properly
	  converted.
	 */
	Uint32 readLE32 ();

	//! Read a 32 bit big endian number.
	/*!
	  If the host is little endian, the number will be properly
	  converted.
	 */
	Uint32 readBE32 ();

	//! Read a 64 bit little endian number.
	/*!
	  If the host is big endian, the number will be properly
	  converted.
	 */
	Uint64 readLE64 ();

	//! Read a 64 bit big endian number.
	/*!
	  If the host is little endian, the number will be properly
	  converted.
	 */
	Uint64 readBE64 ();

	//! Write a 16 bit little endian number.
	/*!
	  If the host is big endian, the number will be properly
	  converted.
	 */
	int writeLE16 (Uint16 value);

	//! Write a 16 bit big endian number.
	/*!
	  If the host is little endian, the number will be properly
	  converted.
	 */
	int writeBE16 (Uint16 value);

	//! Write a 32 bit little endian number.
	/*!
	  If the host is big endian, the number will be properly
	  converted.
	 */
	int writeLE32 (Uint32 value);

	//! Write a 32 bit big endian number.
	/*!
	  If the host is little endian, the number will be properly
	  converted.
	 */
	int writeBE32 (Uint32 value);

	//! Write a 64 bit little endian number.
	/*!
	  If the host is big endian, the number will be properly
	  converted.
	 */
	int writeLE64 (Uint64 value);

	//! Write a 64 bit big endian number.
	/*!
	  If the host is little endian, the number will be properly
	  converted.
	 */
	int writeBE64 (Uint64 value);

	//! \name Streaming operators
	//@{

	//! Return whether the source is big endian.
	/*!
	  \sa setBigEndian ();
	 */
	bool bigEndian ();

	//! Set the source type big endian.
	/*!
	  The default is that the source is little endian.

	  The source type affects the conversion of streamed
	  integers.

	  \param value whether the source is big endian.
	 */
	void setBigEndian (bool value = false);

	//! Types of the data source's status.
	enum
	{
		STAT_OK     = 0x00,
		STAT_EOF    = 0x01,
		STAT_ERROR  = 0x02
	};

	//! Return the status of the data source.
	/*!
	  \note the status is only affected when using
	  the stream operators (<< and >>). No other
	  functions affect the status.

	  \return The bit mask of the source status.

	  \sa setStatus ()
	 */
	Uint8 status () const;

	//! Set the status of the data source.
	/*!
	  This allows you to manually set the source's status.
	  \param value the bitmask of the desired status.
	  \sa status ();
	 */
	void setStatus (Uint8 value = STAT_OK);


	//! Put an integer to the stream.
	RWops & operator<< (Uint8 value);

	//! Put an integer to the stream.
	RWops & operator<< (Sint8 value);

	//! Put an integer to the stream.
	RWops & operator<< (Uint16 value);

	//! Put an integer to the stream.
	RWops & operator<< (Sint16 value);

	//! Put an integer to the stream.
	RWops & operator<< (Uint32 value);

	//! Put an integer to the stream.
	RWops & operator<< (Sint32 value);

	//! Put an integer to the stream.
	RWops & operator<< (Uint64 value);

	//! Put an integer to the stream.
	RWops & operator<< (Sint64 value);

	//! Put a char to the stream.
	RWops & operator<< (char value);

	//! Put a string to the stream.
	/*!
	  First, the length of the string is saved (32 bit
	  number converted to the data source's endian type), and
	  then the string itself (without \0 at the end).
	 */
	RWops & operator<< (const std::string & value);

	/////////////////////////////////////////////////////////////////

	//! Read an integer from the stream.
	RWops & operator>> (Uint8 &value);

	//! Read an integer from the stream.
	RWops & operator>> (Sint8 &value);

	//! Read an integer from the stream.
	RWops & operator>> (Uint16 &value);

	//! Read an integer from the stream.
	RWops & operator>> (Sint16 &value);

	//! Read an integer from the stream.
	RWops & operator>> (Uint32 &value);

	//! Read an integer from the stream.
	RWops & operator>> (Sint32 &value);

	//! Read an integer from the stream.
	RWops & operator>> (Uint64 &value);

	//! Read an integer from the stream.
	RWops & operator>> (Sint64 &value);

	//! Read a char from the stream.
	RWops & operator>> (char &value);

	//! Read a string from the stream.
	/*!
	  The format expected in the source is first an Uint32 that
	  tells the length of the string, and then the string itself
	  (without \0 at the end).
	 */
	RWops & operator>> (std::string & value);

	//@}

protected:
	//! The wrapped SDL::RWops structure.
	SDL::RWops *m_rwops;

	//! Whether source is big endian;
	bool m_big_endian;

	//! The status (a bitmask)
	/*!
	  Possible values:
	  - 0x00 - OK
	  - 0x01 - EOF
	  - 0x02 - ERROR
	 */
	Uint8 m_status;

	std::stack<int> m_pos_stack;

	void updateStatus (int result);

	static int seekMarshaller (SDL::RWops *context, int offset,
				   int whence);
	static int readMarshaller (SDL::RWops *context, void *ptr, int size,
				   int maxnum);
	static int writeMarshaller (SDL::RWops *context, const void *ptr,
				    int size, int num);
	static int closeMarshaller (SDL::RWops *context);

	// Internal position (Used for virtual EOF)
	int m_position;

	//! The virtual file begin offset.
	int m_virtual_begin;

	//! The virtual file size.
	int m_virtual_size;
};

//! File RW operations.
/*!
  This class provides RWops that work with a file.
  You can either open the file manually, and then
  pass the file handle to the constructor, or you
  can pass the file name and the mode.
 */
class FileRWops : public RWops
{
public:
	//! Create a new RWops from file.
	/*!
	  On any error throws an exception.
	  \param filename the name of the file used as data source.
	  \param mode specifies how the file should be opened.
         */
	FileRWops (const std::string & filename, const std::string & mode);

	//! Create a new RWops from file handle pointer.
	/*!
	  On any error throws an exception.
	  \param fp the file handle pointer.
	  \param autoclose whether to close the file on class destruction.
	 */
	FileRWops (FILE *fp, bool autoclose);

	//! Destructor.
	virtual ~FileRWops ();

	//! Overloaded seek implementation.
	virtual int doSeek (int offset, int whence);

	//! Overloaded read implementation.
	virtual int doRead (void *ptr, int size, int maxnum);

	//! Overloaded write implementation.
	virtual int doWrite (const void *ptr, int size, int num);

	//! Overloaded close implementation.
	virtual int doClose ();

	//! Fast get char function.
	/*!
	  This function affects the state.
	 */
	virtual char getChar ();

protected:
	SDL::RWops *m_file_rwops;
};

//! Memory RW operations.
/*!
  This class provides RWops that work with a memory buffer.
 */
class MemRWops : public RWops
{
public:
	//! Create a new RWops from memory buffer.
	/*!
	  On any error throws an exception.
	  \param mem pointer to the memory buffer.
	  \param size size of the memory buffer.
	 */
	MemRWops (void *mem, int size);

	//! Destructor.
	virtual ~MemRWops ();

	//! Overloaded seek implementation.
	virtual int doSeek (int offset, int whence);

	//! Overloaded read implementation.
	virtual int doRead (void *ptr, int size, int maxnum);

	//! Overloaded write implementation.
	virtual int doWrite (const void *ptr, int size, int num);

	//! Overloaded close implementation.
	virtual int doClose ();

	//! Fast get char function.
	/*!
	  This function affects the state.
	 */
	virtual char getChar ();

protected:
	SDL::RWops *m_mem_rwops;
};


//////////////////////////////////////////////////////////////////
//////////////////////// INLINE FUNCTIONS ////////////////////////
//////////////////////////////////////////////////////////////////

inline void
RWops::setVirtualBegin (int offset)
{
	m_virtual_begin = offset;
}

inline void
RWops::setVirtualSize (int size)
{
	m_virtual_size = size;
}

inline int
RWops::seek (int offset, int whence)
{
	return SDL::RWseek (m_rwops, offset, whence);
}

inline int
RWops::tell () const
{
	return SDL::RWtell (m_rwops);
}

inline int
RWops::read (void *ptr, int size, int maxnum)
{
	if (m_rwops)
		return SDL::RWread (m_rwops, ptr, size, maxnum);
	return -1;
}

inline int
RWops::write (const void *ptr, int size, int num)
{
	if (m_rwops)
		return SDL::RWwrite (m_rwops, ptr, size, num);
	return -1;
}

inline Uint16
RWops::readLE16 ()
{
	return SDL::ReadLE16 (m_rwops);
}

inline Uint16
RWops::readBE16 ()
{
	return SDL::ReadBE16 (m_rwops);
}

inline Uint32
RWops::readLE32 ()
{
	return SDL::ReadLE32 (m_rwops);
}

inline Uint32
RWops::readBE32 ()
{
	return SDL::ReadBE32 (m_rwops);
}

inline Uint64
RWops::readLE64 ()
{
	return SDL::ReadLE64 (m_rwops);
}

inline Uint64
RWops::readBE64 ()
{
	return SDL::ReadBE64 (m_rwops);
}

inline int
RWops::writeLE16 (Uint16 value)
{
	return SDL::WriteLE16 (m_rwops, value);
}

inline int
RWops::writeBE16 (Uint16 value)
{
	return SDL::WriteBE16 (m_rwops, value);
}

inline int
RWops::writeLE32 (Uint32 value)
{
	return SDL::WriteLE32 (m_rwops, value);
}

inline int
RWops::writeBE32 (Uint32 value)
{
	return SDL::WriteBE32 (m_rwops, value);
}

inline int
RWops::writeLE64 (Uint64 value)
{
	return SDL::WriteLE64 (m_rwops, value);
}

inline int
RWops::writeBE64 (Uint64 value)
{
	return SDL::WriteBE64 (m_rwops, value);
}

inline bool
RWops::bigEndian ()
{
	return m_big_endian;
}

inline void
RWops::setBigEndian (bool value)
{
	m_big_endian = value;
}

inline Uint8
RWops::status () const
{
	return m_status;
}

inline void
RWops::setStatus (Uint8 value)
{
	m_status = value;
}

inline void
RWops::updateStatus (int result)
{
	if (result == 0)
		m_status |= STAT_EOF;
	else if (result == -1)
		m_status |= STAT_ERROR;
}

} // namespace SEL

#endif // __sel_rwops_h__
